/*
 * rtpStreamer.cpp
 *
 *  Created on: Nov 27, 2011
 *      Author: shaohong
 */

#include <errno.h>
#include <signal.h>
#include <stdio.h>
#include <unistd.h>
#include <time.h>
#include <sys/time.h>

#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <sndfile.h>


#include "RtpStreamer.h"
#include "NmpsException.h"
#include "AppLogger.h"
#include "usageHelper.h"
#include "GlobalVariables.h"

static AppLogger * logger = AppLogger::getInstance("RtpStreamer");

#define RTP_HEADER_SIZE 12
#define AUDIO_PAYLOAD_SIZE 160

//static SNDFILE* sndPCMA = NULL;
//static SF_INFO sfinfoPCMA;
static char audioPacketData[AUDIO_PAYLOAD_SIZE];

// the buffer for UDP pdu
static char udpPDU[RTP_HEADER_SIZE+AUDIO_PAYLOAD_SIZE];

struct sockaddr_in createRemoteSocketAddress(string remoteIP, int remotePort) {
	sockaddr_in * remoteSockAddress = new sockaddr_in();

	memset((char*)(remoteSockAddress), 0, sizeof (* remoteSockAddress));
    remoteSockAddress->sin_family = AF_INET;
    remoteSockAddress->sin_port = htons(remotePort);
    if(inet_aton(remoteIP.c_str(), & remoteSockAddress->sin_addr) == 0){
        throw NmpsException("inet_aton() failed!");
    }

//    fprintf(stderr, "remoteSockAddress = %d, %s:%d\n", remoteSockAddress->sin_family,
//			inet_ntoa(remoteSockAddress->sin_addr), ntohs(remoteSockAddress->sin_port));
    logger->info("remoteSockAddress = %d, %s:%d\n", remoteSockAddress->sin_family, inet_ntoa(remoteSockAddress->sin_addr), ntohs(remoteSockAddress->sin_port));

    return (* remoteSockAddress);
}

namespace nmps {

RtpStreamer::RtpStreamer() {

	timerInterval = 20; // so by default our RTP interval is 20 ms
	paused = false;

	packetSize = AUDIO_PAYLOAD_SIZE;
	unSentDataStartPos = 0; // the starting position for data not send, in the currentPcmDataBlock
	currPcmDataBlock = NULL;
	playoutBuffer = new PcmPlayoutBuffer();
	header.setUseMarker(true); // let the first packet use marker
}

RtpStreamer::~RtpStreamer() {
}

PcmPlayoutBuffer *RtpStreamer::getPlayoutBuffer() {
	return playoutBuffer;
}

void RtpStreamer::addRecipient(string recipientIP, int recipientPort) {
	pair<string, int> recipient = make_pair(recipientIP, recipientPort);

//	fprintf(stderr, "RtpStreamer::addRecipient(%s, %d)\n", recipientIP.c_str(),
//			recipientPort);

	recipientList.push_back(recipient);

	this->initSockets();
}

void RtpStreamer::removeRecipient(string recipientIP, int recipientPort) {
	list<pair<string, int> >::iterator itr = recipientList.begin();
	while (itr != recipientList.end()) {
		if (0 == itr->first.compare(recipientIP)) {
			recipientList.erase(itr);
			break;
		}
		itr++;
	}

	this->initSockets();

//	logger->error("didn't find %s:%d in the recipient", recipientIP.c_str(),
//			recipientPort);
}

const list<pair<string, int> > * RtpStreamer::getRecipientList() {
	return & this->recipientList;
}

void RtpStreamer::pause() {
	paused = true;
}

void RtpStreamer::resume() {
	paused = false;
}

bool RtpStreamer::isPaused() {
	return paused;
}

RtpHeader RtpStreamer::getRTPHeader() {
	return header;
}

//whether this RtpStreamer has any recipients
bool RtpStreamer::hasRecipient() {
	return !remoteSockets.empty();
}

void RtpStreamer::clearAudioPlayoutBuffer() {
	logger->debug("clearAudioPlayoutBuffer()");

	this->playoutBuffer->clearAllData();
}


void RtpStreamer::initSockets() {
	//remove all existing sockets
	remoteSockets.clear();

	list<pair<string, int> >::iterator itr = recipientList.begin();
	while (itr != recipientList.end()) {
		string remoteIP = itr->first;
		int remotePort = itr->second;

//		fprintf(stderr, "streaming target: %s:%d\n", remoteIP.c_str(), remotePort);

		logger->debug("streaming target: %s:%d", remoteIP.c_str(), remotePort);
		//create socket address for the remote IP and port pair;
		struct sockaddr_in remoteSocketAddress = createRemoteSocketAddress(
				remoteIP, remotePort);
		remoteSockets.push_back(remoteSocketAddress);

		itr++;
	}

}

void RtpStreamer::initialize() {
	logger->info("RTP streamer initialize()");

	udpSocketFD = socket(AF_INET, SOCK_DGRAM, 0);
	if (-1 == udpSocketFD) {
		throw NmpsException("failed to create udp socket!");
	}

//	bzero(&sfinfoPCMA, sizeof(sfinfoPCMA));
//	sfinfoPCMA.samplerate = 8000;
//	sfinfoPCMA.channels = 1;
//	sfinfoPCMA.format = SF_FORMAT_WAV | SF_FORMAT_ALAW;
//
//	sndPCMA = sf_open("nmps_pcma.wav", SFM_WRITE, &sfinfoPCMA);
//	if (sndPCMA == NULL) {
//		fprintf(stderr, "Cannot open output file!\n");
//		exit(EXIT_FAILURE);
//	}

}

/**
 * send audio packet to peer over RTP
 */
void RtpStreamer::sendAudioPacket(char *audioData, int audioDataSize) {
//	sf_write_raw(sndPCMA, audioData, audioDataSize);

	//get the RTP header
	const unsigned char * rtpHeader = header.getPacketizedForm();

	//change the timestamp and sequence number of the RTP header
	header.incrementSeqNum();
	header.setTimeStamp(header.getTimeStamp() + audioDataSize);
	header.setUseMarker(false); // let the first packet use marker

	// we have the header and we have the audio data. Let's send them over the UDP socket
	memcpy(udpPDU, rtpHeader, RTP_HEADER_SIZE);
	memcpy(udpPDU + RTP_HEADER_SIZE, audioPacketData, audioDataSize);

	if (hasRecipient()) {
		// go through the recipient list and send the audio to recipients
		list<sockaddr_in>::iterator itr = remoteSockets.begin();
		while (itr != remoteSockets.end()) {
			sockaddr_in remoteSockAddress = *itr;
			itr++;
			sendto(	udpSocketFD, (const void *) udpPDU,
					(unsigned long) (RTP_HEADER_SIZE + audioDataSize),
					0, (const sockaddr *) &remoteSockAddress, sizeof(remoteSockAddress));
		}
	}
		//save into global stats the PCMA frames sent
	incrementPcmFrameSent(&globalStreamerStats);
}

void RtpStreamer::sendDataOverRTP(bool muted) {

	//at first, initialize everything to be zero
	memset(audioPacketData, 0, AUDIO_PAYLOAD_SIZE);

	// if there're left over data, in the current PCM Data block,
	// push it in the outgoing buffer.
	if (currPcmDataBlock == NULL) {
		unSentDataStartPos = 0;
		currPcmDataBlock = getPlayoutBuffer()->deQueuePcmDataBlock();

		if (NULL == currPcmDataBlock) {
			unSentDataStartPos = 0;
			//fill the packet with blanks
//			memset(audioPacketData,0,packetSize);
		} else {

			if (currPcmDataBlock->getDataSize() > packetSize) {
				// get something good. This is what we want
				currPcmDataBlock->getPcmData(0, packetSize, audioPacketData);
				unSentDataStartPos = packetSize;
			} else {
				// get something not very good
				//let's just write whatever we get and fill the rest with 0s
				int bytesWritten = 0;
				int roomLeft = packetSize;
				while ((bytesWritten < packetSize) && (currPcmDataBlock != NULL)) {

					roomLeft = packetSize - bytesWritten;
					if (roomLeft > currPcmDataBlock->getDataSize()) {
						// write what we have and continue looking for more
						currPcmDataBlock->getPcmData(0,
								currPcmDataBlock->getDataSize(),
								audioPacketData + bytesWritten);
						bytesWritten += currPcmDataBlock->getDataSize();
						currPcmDataBlock =
								getPlayoutBuffer()->deQueuePcmDataBlock();
						unSentDataStartPos = 0;
						continue;
					} else {
						// fill in the room left.
						currPcmDataBlock->getPcmData(0,
								roomLeft,
								audioPacketData + bytesWritten);
						bytesWritten += roomLeft;
						unSentDataStartPos = roomLeft;
						break;
					}

				} // end of while


			}
		}
	} else {
		// there are some left over data
		int currBlockSize = currPcmDataBlock->getDataSize();
		int unsentInCurrBlock = currBlockSize - unSentDataStartPos;
		// if left over is big enough to fill the packet, go ahead
		if (unsentInCurrBlock > packetSize) {
			currPcmDataBlock->getPcmData(unSentDataStartPos,
					unSentDataStartPos + packetSize, audioPacketData);
			unSentDataStartPos = unSentDataStartPos + packetSize;
		} else {
			// copy whatever left and then get a new block to fill the left overs
			currPcmDataBlock->getPcmData(unSentDataStartPos,
					unSentDataStartPos + unsentInCurrBlock, audioPacketData);

			currPcmDataBlock = playoutBuffer->deQueuePcmDataBlock();
			int toBeExtractedFurther = packetSize - unsentInCurrBlock;
			if (currPcmDataBlock == NULL) {
				//fill the leftover with blanks (it already is at the very beginning)
				unSentDataStartPos = 0;

			} else {
				if (toBeExtractedFurther > currPcmDataBlock->getDataSize()) {
					// use up current block and leave!
					currPcmDataBlock->getPcmData(0, currPcmDataBlock->getDataSize(),
							audioPacketData + unsentInCurrBlock);
					unSentDataStartPos = 0;
					currPcmDataBlock = NULL;

				} else {
					currPcmDataBlock->getPcmData(0, toBeExtractedFurther,
							audioPacketData + unsentInCurrBlock);
					unSentDataStartPos = toBeExtractedFurther;
				}
			}

		}

	}

	// don't send the real audio to client, we're told to do so
	if (muted) {
		memset(audioPacketData, 0, AUDIO_PAYLOAD_SIZE);
	}
	// send the packet to remote
	sendAudioPacket(audioPacketData, packetSize);

}

} /* namespace nmps */
